Skip to content

optimize unnecessary copy#573

Merged
theomonnom merged 5 commits intomainfrom
theo/no-copy
Feb 15, 2026
Merged

optimize unnecessary copy#573
theomonnom merged 5 commits intomainfrom
theo/no-copy

Conversation

@theomonnom
Copy link
Member

  Video (1080p RGBA/I420) — avg ms/frame                                                                                                               
  ┌────────────────────────┬──────────┬───────────┬─────────┐
  │       Operation        │ Baseline │ Optimized │ Speedup │                                                                                          
  ├────────────────────────┼──────────┼───────────┼─────────┤                              
  │ RGBA bytearray (8.3MB) │ 0.959    │ 0.414     │ 2.3x    │
  ├────────────────────────┼──────────┼───────────┼─────────┤
  │ RGBA bytes (8.3MB)     │ 0.886    │ 0.326     │ 2.7x    │
  ├────────────────────────┼──────────┼───────────┼─────────┤
  │ I420 bytearray (3.1MB) │ 0.185    │ 0.123     │ 1.5x    │
  ├────────────────────────┼──────────┼───────────┼─────────┤
  │ I420 bytes (3.1MB)     │ 0.226    │ 0.128     │ 1.8x    │
  └────────────────────────┴──────────┴───────────┴─────────┘
  Audio 10ms frames (960 bytes) — avg ms
  ┌───────────────────────┬──────────┬───────────┬─────────┐
  │       Operation       │ Baseline │ Optimized │ Speedup │
  ├───────────────────────┼──────────┼───────────┼─────────┤
  │ AudioFrame(bytearray) │ 0.0031   │ 0.0011    │ 2.8x    │
  ├───────────────────────┼──────────┼───────────┼─────────┤
  │ AudioFrame(bytes)     │ 0.0023   │ 0.0010    │ 2.3x    │
  ├───────────────────────┼──────────┼───────────┼─────────┤
  │ capture(bytearray)    │ 0.0688   │ 0.0676    │ ~same   │
  ├───────────────────────┼──────────┼───────────┼─────────┤
  │ capture(bytes)        │ 0.0609   │ 0.0616    │ ~same   │
  ├───────────────────────┼──────────┼───────────┼─────────┤
  │ resample(bytearray)   │ 0.0084   │ 0.0077    │ ~same   │
  ├───────────────────────┼──────────┼───────────┼─────────┤
  │ resample(bytes)       │ 0.0085   │ 0.0081    │ ~same   │
  └───────────────────────┴──────────┴───────────┴─────────┘
  Audio 200ms frames (19.2KB) — avg ms
  ┌───────────────────────┬──────────┬───────────┬─────────┐
  │       Operation       │ Baseline │ Optimized │ Speedup │
  ├───────────────────────┼──────────┼───────────┼─────────┤
  │ AudioFrame(bytearray) │ 0.0010   │ 0.0003    │ 3.3x    │
  ├───────────────────────┼──────────┼───────────┼─────────┤
  │ AudioFrame(bytes)     │ 0.0011   │ 0.0003    │ 3.7x    │
  ├───────────────────────┼──────────┼───────────┼─────────┤
  │ capture(bytearray)    │ 0.0666   │ 0.0649    │ ~same   │
  ├───────────────────────┼──────────┼───────────┼─────────┤
  │ capture(bytes)        │ 0.0761   │ 0.0741    │ ~same   │
  ├───────────────────────┼──────────┼───────────┼─────────┤
  │ resample(bytearray)   │ 0.0686   │ 0.0680    │ ~same   │
  ├───────────────────────┼──────────┼───────────┼─────────┤
  │ resample(bytes)       │ 0.0685   │ 0.0684    │ ~same   │
  └───────────────────────┴──────────┴───────────┴─────────┘

@theomonnom theomonnom requested review from a team February 10, 2026 04:27
Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 potential issues.

View 5 additional findings in Devin Review.

Open in Devin Review

Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 7 additional findings in Devin Review.

Open in Devin Review

@theomonnom theomonnom merged commit 13563e8 into main Feb 15, 2026
27 checks passed
@theomonnom theomonnom deleted the theo/no-copy branch February 15, 2026 07:35
Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

View 9 additional findings in Devin Review.

Open in Devin Review

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 process_reverse_stream checks wrong response field, silently ignoring errors

In process_reverse_stream, the error check on line 93 reads resp.apm_process_stream.error instead of resp.apm_process_reverse_stream.error. This means any error returned by the FFI reverse-stream processing call is silently ignored.

Root Cause and Impact

The method sends a request via req.apm_process_reverse_stream but checks the response on the wrong oneof field resp.apm_process_stream.error (copy-paste from process_stream). Since protobuf oneofs default to empty/falsy when the wrong field is accessed, resp.apm_process_stream.error will always be empty after an apm_process_reverse_stream request, so the RuntimeError is never raised even when the FFI call fails.

Compare with the correct check in process_stream at apm.py:65-66:

if resp.apm_process_stream.error:
    raise RuntimeError(resp.apm_process_stream.error)

The process_reverse_stream method at apm.py:93-94 should mirror this but for the reverse stream:

if resp.apm_process_reverse_stream.error:  # NOT apm_process_stream
    raise RuntimeError(resp.apm_process_reverse_stream.error)

Impact: Errors during reverse stream processing (used for echo cancellation) are silently swallowed, making it very difficult to diagnose audio processing failures in full-duplex setups.

(Refers to lines 93-94)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

if isinstance(data, memoryview):
if not data.contiguous:
raise ValueError("memoryview must be contiguous")
if data.nbytes != len(data.obj): # type: ignore[arg-type]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 _buffer_supported_or_raise incorrectly rejects valid non-sliced memoryviews backed by non-byte-sized element types

The sliced-memoryview check at line 53 compares data.nbytes (always in bytes) with len(data.obj) (element count for non-byte buffer types). This causes valid, non-sliced memoryviews backed by objects like array.array, ctypes arrays, or numpy arrays to be incorrectly rejected.

Root Cause and Impact

For bytes and bytearray, len() returns byte count, so the check works. But for other buffer-exporting objects, len() returns element count:

  • array.array('h', range(10)): len() = 10, but memoryview(...).nbytes = 20
  • numpy.zeros(10, dtype=np.int16): len() = 10, but memoryview(...).nbytes = 20
  • ctypes (c_int16 * 10)(): len() = 10, but memoryview(...).nbytes = 20

When a user passes memoryview(array.array('h', samples)) or memoryview(numpy_int16_array) as the data argument to AudioFrame() or VideoFrame(), _buffer_supported_or_raise raises ValueError("sliced memoryviews are not supported") even though the memoryview is perfectly valid and non-sliced.

Impact: Users passing memoryviews of non-byte-element buffer objects (common with numpy or array.array workflows) will get a spurious ValueError.

Suggested change
if data.nbytes != len(data.obj): # type: ignore[arg-type]
if data.nbytes != memoryview(data.obj).nbytes:
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants